package edu.java.homework.hw08.ipj09.exer01;

public class Fraction {

    // --- Instance Variables --------------------------------------------------
    protected int sign = 0;
    protected int n = 0;
    protected int d = 0;

    // --- Constructors --------------------------------------------------------

    public Fraction() {
	this(0, 0, 0);
    }

    public Fraction(int sign, int n, int d) {
	this.sign = sign;
	this.n = n;
	this.d = d;
    }

    public Fraction(Fraction fraction) {
	this(fraction.getSign(), fraction.getN(), fraction.getD());
    }

    // --- Getter and Setters --------------------------------------------------
    public int getSign() {
	return sign;
    }

    public void setSign(int sign) {
	// check input parametar
	if (sign != -1 || sign != 1) {
	    throw new IllegalArgumentException("Incorrect input for sign.");
	}
	this.sign = sign;
    }

    public int getN() {
	return n;
    }

    public void setN(int n) {
	if (n < 0) {
	    throw new IllegalArgumentException("Incorrect input for numerator.");
	}
	this.n = n;
    }

    public int getD() {
	return d;
    }

    public void setD(int d) {
	if (d <= 0) {
	    throw new IllegalArgumentException(
		    "Incorrect input for denomerator.");
	}
	this.d = d;
    }

    // Methods -----------------------------------------------------------------

    @Override
    public String toString() {
	if (getSign() == -1) {
	    return "-" + getN() + "/" + getD();
	} else {
	    return getN() + "/" + getD();
	}

    }

    /**
     * Fraction accumulation
     * 
     * @param fr
     *            --> fraction to adding
     * @return Fraction object which is the result of accumulation
     */
    public Fraction add(Fraction fr) {
	int tmpNumb1 = this.getN() * fr.getD();
	int tmpNumb2 = fr.getN() * this.getD();
	int newNumerator = (tmpNumb1 * getSign()) + (tmpNumb2 * fr.getSign());
	int newDenomerator = fr.getD() * this.getD();
	if (newNumerator >= 0) {
	    return new Fraction(1, Math.abs(newNumerator), newDenomerator);
	} else {
	    return new Fraction(-1, Math.abs(newNumerator), newDenomerator);
	}
    }

    /**
     * 
     * @param fr
     *            --> fraction to subtraction
     * @return Fraction object which is the result of subtraction
     */
    public Fraction sub(Fraction fr) {
	int tmpNumb1 = this.getN() * fr.getD();
	int tmpNumb2 = fr.getN() * this.getD();
	int newNumerator = (tmpNumb1 * getSign()) - (tmpNumb2 * fr.getSign());
	int newDenomerator = fr.getD() * this.getD();
	if (newNumerator >= 0) {
	    return new Fraction(1, Math.abs(newNumerator), newDenomerator);
	} else {
	    return new Fraction(-1, Math.abs(newNumerator), newDenomerator);
	}
    }

    /**
     * 
     * Multiplication of fractions
     */
    public Fraction mult(Fraction fr) {
	int sign = getSign() * fr.getSign();
	int newNumerator = getN() * fr.getN();
	int newDenomerator = getD() * fr.getD();
	return new Fraction(sign, newNumerator, newDenomerator);
    }

    public Fraction div(Fraction fr) {
	if (fr.isZero()) {
	    throw new IllegalArgumentException("Illegal operation: / by zero");
	}
	Fraction reciprocalFraction = fr.getReciprocalFraction();
	return mult(reciprocalFraction);

    }

    /**
     * Return absolute value of the fraction
     */
    public static Fraction abs(Fraction fr) {
	return new Fraction(Math.abs(fr.getSign()), fr.getN(), fr.getD());
    }

    /**
     * return the maximum of the 2 fractions
     */

    public static Fraction max(Fraction fr1, Fraction fr2) {
	int modifiedNumerator1 = fr1.getN() * fr2.getD() * fr1.getSign();
	int modifiedNumerator2 = fr2.getN() * fr1.getD() * fr2.getSign();

	return (modifiedNumerator1 > modifiedNumerator2) ? fr1 : fr2;
    }

    /**
     * return the minimal of the 2 fractions
     */
    public static Fraction min(Fraction fr1, Fraction fr2) {
	return (max(fr1, fr2).equals(fr2)) ? fr1 : fr2;
    }

    /**
     * return the fraction in pow
     */
    public static Fraction pow(Fraction fr1, double degree) {
	int degreeSign = (int) Math.pow(fr1.getSign(), degree);
	int degreeNumerator = (int) Math.pow(fr1.getN(), degree);
	int degreeDenomerator = (int) Math.pow(fr1.getD(), degree);
	return new Fraction(degreeSign, degreeNumerator, degreeDenomerator);
    }

    /**
     * return the square root of the fraction. The fraction would be positive
     */
    public static double sqrt(Fraction fr1) {
	if (fr1.getSign() == -1) {
	    throw new IllegalArgumentException(
		    "The fraction is with negative sign."
			    + " The fraction would be positive.");
	}

	return Math.sqrt(fr1.getN()) / Math.sqrt(fr1.getD());
    }

    /**
     * return to decimal fraction
     */
    public double convertToDecimalFraction() {
	double decimalFraction = (getN() * 1.0) / getD();
	return getSign() * decimalFraction;
    }

    /**
     * @return Math.pow(Math.E, fr1)
     */
    public static double exp(Fraction fr1) {
	return Math.pow(Math.E, fr1.convertToDecimalFraction());
    }

    /**
     * @return Math.log(fr1)
     */
    public static double ln(Fraction fr1) {
	return Math.log(fr1.convertToDecimalFraction());
    }

    /**
     * return the natural number if the denimerator is 1
     */
    public int convertToNaturalNumber() {
	if (getD() != 1) {
	    throw new IllegalArgumentException(
		    "THe denomerator is not is not 1.");
	} else {
	    return getN() * getSign();
	}
    }

    /**
     * return Fraction where the numerator is GCD of numerators of the 2
     * fractions. The denomerator is GCD of the denomerators of the 2 fractions.
     * The sign is positive
     */
    protected static Fraction nod(Fraction a, Fraction b) {
	int gcdNumerators = nod(a.getN(), b.getN());
	int gcdDenomerators = nod(a.getD(), b.getD());
	return new Fraction(1, gcdNumerators, gcdDenomerators);
    }

    /**
     * NOK of the 2 fractions
     */
    protected static int nok(Fraction a, Fraction b) {
	Fraction multiFraction = a.mult(b);
	double decimalFractionNumerator = multiFraction
		.convertToDecimalFraction();
	double decimalFractionDenomerator = nod(a, b)
		.convertToDecimalFraction();
	return (int) (decimalFractionNumerator / decimalFractionDenomerator);
    }

    /**
     * reduce the numerator and denomerator of the fraction
     */
    protected void elimination() {
	int gcd = nod(getN(), getD());
	int newNumerator = (int) (getN() / gcd);
	int newDenomerator = (int) (getD() / gcd);
	setN(newNumerator);
	setD(newDenomerator);
    }

    /**
     *@return false if the fraction can be converted to integer number or 0.
     *         Return true is opposite.
     */
    public boolean isFraction() {
	this.elimination();
	return getD() != 1;
    }

    @Override
    public boolean equals(Object obj) {
	Fraction frac;
	if (obj instanceof Fraction) {
	    frac = (Fraction) obj;
	} else {
	    throw new IllegalArgumentException(
		    "The input object is not a fraction");
	}

	return (this.getSign() == frac.getSign())
		&& (this.getN() == frac.getN()) && (this.getD() == frac.getD());

    }

    // --- Methods Auxiliary ---------------------------------------------------

    private boolean isZero() {
	return this.getN() == 0 && this.getD() == 1;
    }

    private Fraction getReciprocalFraction() {
	return new Fraction(this.getSign(), this.getD(), this.getN());
    }

    // 
    private int gcd(int a, int b) {
	while (a != b) {
	    if (a > b) {
		a = a - b;
	    } else {
		b = b - a;
	    }
	}
	return a;
    }

    /**
     * @return the gcd of "a" and "b"
     */
    protected static int nod(int a, int b) {

	while (b != 0) {
	    int t = b;
	    b = a % b;
	    a = t;
	}

	return a;
    }
}
